#include "gl_interface.hpp"
namespace GL_Interface
{
#define RENDER_WIDTH 1024.0
#define RENDER_HEIGHT 769.0
#define SHADOW_MAP_RATIO 2

using namespace TestbedGL;

boost::shared_array<unsigned int> vector_to_uarray(std::vector<unsigned int>&vec)
{
    boost::shared_array<unsigned int> ret (new unsigned int[vec.size()]);
    for (unsigned int i = 0; i < vec.size(); i++)
        ret[i] = vec[i];
    return ret;
}

boost::shared_array<float> vector_to_array(std::vector<float>&vec)
{
    boost::shared_array<float> ret (new float[vec.size()]);
    for (unsigned int i = 0; i < vec.size(); i++)
        ret[i] = vec[i];
    return ret;
}
inline int func(int size_x,int size_y, int border_x,int border_y,int i,int j)
{
    int decrement = 0;
    int start_index = (border_x + 1) * size_y + border_y + 1;
    int index = i*size_y + j;
    int interval = size_y - border_y*2 - 2;

    if(index > start_index)
    {
        int interval_count = MIN(interval,(index - start_index)/size_y +1);
        decrement = interval_count * interval;
    }

    return i * size_y +  j  - decrement;
}

//Don't use it now. Since it's only for future implementation of infinite terrain stuff
boost::shared_ptr<TestbedGL::SceneNode> GraphicsLibrary::make_nested_grid(Heightmap &map,std::string name,unsigned int size_x,unsigned int size_y,unsigned int border_x,unsigned int border_y,float tile_x,float tile_y,float u,float v)
{

    boost::shared_ptr<Geometry> geom = boost::shared_ptr<Geometry>(new Geometry());
    geom->name =name;
    nodes[name] = boost::shared_ptr<SceneNode>(new SceneNode());
    nodes[name]->base_matrix = Matrix4GL::identity();

    unsigned int real_x = size_x+1;
    unsigned int real_y = size_y+1;


    unsigned int vertex_count = real_x*real_y;// - (real_x - border_x*2 - 2)*(real_x - border_x*2 - 2);
    fflush(stdout);
    boost::shared_array<float>position = boost::shared_array<float>(new float[vertex_count*3]);
    boost::shared_array<float> normals = boost::shared_array<float>(new float[vertex_count*3]);
    boost::shared_array<float> uv = boost::shared_array<float>(new float[vertex_count*2]);
    int counter = 0;
    for(unsigned int i = 0; i < real_x; i++)

        for(unsigned int j = 0; j < real_y; j++)
        {
            if((i > border_x && i < size_x - border_x) && ( j > border_y && j< size_y - border_y))
            {

            }
            else
            {

                position[counter*3]   = i * tile_x;
                position[counter*3+1] = 0.0f;
                position[counter*3+2] = j * tile_y;

                uv[counter*2]   = i * 1.0f/(float) size_x;
                uv[counter*2+1] = j *1.0f/(float) size_y;

                normals[counter*3]   = 0.0f;
                normals[counter*3+1] = 1.0f;
                normals[counter*3+2] = 0.0f;

                counter +=1;
            }

        }
    int index_count = size_x*size_y- (size_x - border_x*2)*(size_y - border_y*2);
    boost::shared_array<unsigned int> indecies = boost::shared_array<unsigned int>(new unsigned int[index_count*6]);

    counter = 0;
    for(unsigned int i = 0; i < size_x; i++)
        for(unsigned int j = 0; j < size_y; j++)
        {

            if((i > border_x - 1 && i < size_x - border_x) && ( j > border_y - 1 && j< size_y - border_y))
            {
            }
            else
            {
                fflush(stdout);
                indecies[counter*6] = func(real_x,real_y,border_x,border_y,i,j);
                indecies[counter*6+1] = func(real_x,real_y,border_x,border_y,i,j+1);
                indecies[counter*6+2] = func(real_x,real_y,border_x,border_y,i+1,j+1);

                indecies[counter*6+5] = func(real_x,real_y,border_x,border_y,i,j);
                indecies[counter*6+4] = func(real_x,real_y,border_x,border_y,i+1,j);
                indecies[counter*6+3] = func(real_x,real_y,border_x,border_y,i+1,j+1);
                counter+=1;
            }

        }


    FloatArray pos(3, vertex_count  , 3, position,"positions");
    FloatArray norm(3, vertex_count  , 3, normals,"normals");
    FloatArray texc(2, vertex_count  , 2, uv,"texcoords");

    FaceArray faces(index_count*6,indecies);
    geom->faces = faces;

    geom->link.set_vertex_array(pos);
    geom->link.set_normal_array(norm);
    geom->link.add_tex_coord_array(texc);
    geometries[name] = geom;
    //geom->initVBO();
    nodes[name]->geometry_ids.push_back(geometries[name]);
    nodes[name]->name = name;
    return nodes[name];

}


boost::shared_ptr<TestbedGL::SceneNode> GraphicsLibrary::make_grid(Heightmap &map,std::string name,unsigned int size_x,unsigned int size_y,float tile_x,float tile_y,float u,float v)
{

    boost::shared_ptr<Geometry> geom = boost::shared_ptr<Geometry>(new Geometry());
    geom->name =name;
    nodes[name] = boost::shared_ptr<SceneNode>(new SceneNode());
    nodes[name]->base_matrix = Matrix4GL::identity();

    unsigned int real_x = (size_x+1);
    unsigned int real_y = (size_y+1);

    unsigned int vertex_count = (real_x)*(real_y);

    boost::shared_array<float>position = boost::shared_array<float>(new float[vertex_count*3]);
    boost::shared_array<float> normals = boost::shared_array<float>(new float[vertex_count*3]);
    boost::shared_array<float> uv = boost::shared_array<float>(new float[vertex_count*2]);
    for(unsigned int i = 0; i < real_x; i++)
        for(unsigned int j = 0; j < real_y; j++)
        {


            position[(i*real_y+j)*3]   = i * tile_x;
            position[(i*real_y+j)*3+1] = map.get_level(j,i);
            position[(i*real_y+j)*3+2] = j * tile_y;

            uv[(i*real_y+j)*2]   = 1.0f/size_x * (float)i;
            uv[(i*real_y+j)*2+1] = 1.0f/size_y * (float)j;

            normals[(i*real_y+j)*3]   = 0.0f;
            normals[(i*real_y+j)*3+1] = 1.0f;
            normals[(i*real_y+j)*3+2] = 0.0f;

        }
    boost::shared_array<unsigned int> indecies = boost::shared_array<unsigned int>(new unsigned int[size_x*size_y*6]);
    for(unsigned int i = 0; i < size_x; i++)
        for(unsigned int j = 0; j < size_y; j++)
        {
            indecies[(i*size_y+j)*6] = i*real_y+j;
            indecies[(i*size_y+j)*6+1] =i*real_y+j+1;
            indecies[(i*size_y+j)*6+2] = (i+1)*real_y+j+1;

            indecies[(i*size_y+j)*6+5] = i*real_y+j;
            indecies[(i*size_y+j)*6+4] = (i+1)*real_y+j;
            indecies[(i*size_y+j)*6+3] = (i+1)*real_y+j+1;
        }


    FloatArray pos(3, vertex_count  , 3, position,"positions");
    FloatArray norm(3, vertex_count  , 3, normals,"normals");
    FloatArray texc(2, vertex_count  , 2, uv,"texcoords");

    FaceArray faces(size_x*size_y*6,indecies);
    geom->faces = faces;

    geom->link.set_vertex_array(pos);
    geom->link.set_normal_array(norm);
    geom->link.add_tex_coord_array(texc);
    geometries[name] = geom;
    geom->initVBO();
    nodes[name]->geometry_ids.push_back(geometries[name]);
    nodes[name]->name = name;
    return nodes[name];

}

struct TmpArray
{
    unsigned int offset;
    unsigned int width;
    std::string semantic;
    std::vector <float> input_array;
    boost::shared_array<float> output_array;
    TmpArray()
    {
        offset = 0;
        width = 0;
    }
};

struct IndexedArray
{
    unsigned int max_offset;
    boost::unordered_map<std::string,int> index;
    std::vector<TmpArray>farrays;

    std::vector<unsigned int> input_indecies;
    boost::shared_array<unsigned int> output_indecies;
    IndexedArray()
    {
        max_offset = 0;
    }


};


int string_to_uint_array(std::string s,std::vector<unsigned int>&retvec)
{
    std::string buffer;
    if (s.size()==0)
        return 1;
    for (std::string::iterator str = s.begin(); str != s.end(); str++)
    {
        if (*str==' '||*str=='\t')
        {
            if (buffer.size()>0)
            {
                unsigned int i = atoi(buffer.c_str());
                retvec.push_back(i);
            }
            buffer.clear();
        }
        else
        {
            buffer.append(1,*str);
        }
    }

    if (buffer.size()>0)
    {

        unsigned int i = atoi(buffer.c_str());
        retvec.push_back(i);
    }
    return 0;
}

int string_to_float_array(std::string s,std::vector<float>&retvec)
{
    std::string buffer;

    if (s.size()<1)
        return 1;

    for (std::string::iterator str = s.begin(); str != s.end(); str++)
    {
        if (*str==' '||*str=='\t')
        {
            if (buffer.size() > 0)
            {
                float f = (float)atof(buffer.c_str());
                retvec.push_back(f);
            }
            buffer.clear();
        }
        else
        {
            buffer.append(1,*str);
        }
    }
    if (buffer.size()>0)
    {
        float i = (float)atof(buffer.c_str());
        retvec.push_back(i);
    }

    return 0;
}


void bake_indecies(Entry &entry,IndexedArray &array)
{

}

struct index
{
    std::vector<unsigned int> indecies;
    bool operator == (const index& dst)
    {
        return  std::equal( indecies.begin(), indecies.end(), dst.indecies.begin() );
    }
};

void GraphicsLibrary::load_collada(std::string filename)
{
    Parser ps;

    try
    {
        ps.parse(filename.c_str());
    }
    catch (const char *str)
    {
        std::cout << str <<std::endl;
        return;
    }
    std::vector<boost::shared_ptr<Entry> > geom = ps.r().g("COLLADA")[0]->g("library_geometries")[0]->g("geometry");
    //std::cout << geom.size() << std::endl;
    std::vector<IndexedArray>array;

    for( std::vector<boost::shared_ptr<Entry> >::iterator git = geom.begin(); git != geom.end(); git++)
    {
        IndexedArray array;
        Entry triangles = *( (*git)->g("mesh")[0]->g("triangles")[0] );
        std::vector<boost::shared_ptr<Entry> > inputs = triangles.g("input");
        for(std::vector<boost::shared_ptr<Entry> >::iterator in_it = inputs.begin(); in_it != inputs.end(); in_it++)
        {
            std::string input_id = std::string((*in_it)->attributes["source"]).erase(0,1);
            unsigned int offset = atoi((*in_it)->attributes["offset"].c_str());

            array.max_offset = MAX(array.max_offset,offset);

            if(ps.get_by_id(input_id)->type == "vertices")
            {
                Entry vertices = *(ps.get_by_id(input_id));
                std::vector<boost::shared_ptr<Entry> > vinputs = vertices.g("input");
                for(std::vector<boost::shared_ptr<Entry> >::iterator vin_it = vinputs.begin(); vin_it != vinputs.end(); vin_it++)
                {

                    std::string vinput_id =std::string((*vin_it)->attributes["source"]).erase(0,1);
                    Entry source = *(ps.get_by_id(vinput_id));
                    //Just to avoid copying.
                    array.farrays.push_back(TmpArray());
                    array.farrays.rbegin()->semantic = (*vin_it)->attributes["semantic"];
                    array.farrays.rbegin()->width = atoi(source.g("technique_common")[0]->g("accessor")[0]->attributes["stride"].c_str());
                    array.farrays.rbegin()->offset = offset;

                    //array.index[source.attributes["id"]] =(array.input_arrays.size()-1);
                    string_to_float_array(source.g("float_array")[0]->data,array.farrays.rbegin()->input_array);
                    //std::cout << offset << " " << vinput_id << std::endl;
                }

            }
            else if (ps.get_by_id(input_id)->type == "source")
            {
                Entry source = *(ps.get_by_id(input_id));
                //Just to avoid copying.
                array.farrays.push_back(TmpArray());
                array.farrays.rbegin()->semantic = (*in_it)->attributes["semantic"];
                array.farrays.rbegin()->width = atoi(source.g("technique_common")[0]->g("accessor")[0]->attributes["stride"].c_str());
                array.farrays.rbegin()->offset = offset;


                string_to_float_array(source.g("float_array")[0]->data,array.farrays.rbegin()->input_array);

                //std::cout << offset << " " << input_id << std::endl;
            }

        }

        string_to_uint_array((triangles.g("p")[0])->data,array.input_indecies);

        std::vector<index>cindecies;
        std::vector<unsigned int>indecies;
        //std::cout << array.max_offset << std::endl;
        for(unsigned int iit =  0; iit <  array.input_indecies.size(); iit+=(array.max_offset+1))
        {
            index tmp;
            for(int i = 0; i < array.max_offset+1; i++)
            {
                tmp.indecies.push_back(array.input_indecies[iit+i]);
            }

            cindecies.push_back(tmp);
            indecies.push_back(cindecies.size()-1);

        }

        for(unsigned int at = 0; at < array.farrays.size(); at++)
        {
            array.farrays[at].output_array = boost::shared_array<float>(new float[cindecies.size()*(array.farrays[at].width)]);
        }



        for(unsigned int cindex = 0; cindex < cindecies.size(); cindex++)
        {
            for(unsigned int inpt_cnt = 0; inpt_cnt < array.farrays.size(); inpt_cnt++)
            {
                for(unsigned int width = 0; width < array.farrays[inpt_cnt].width; width++)
                {

                    array.farrays[inpt_cnt].output_array[cindex * array.farrays[inpt_cnt].width+width] =
                        array.farrays[inpt_cnt].input_array[
                            cindecies[cindex].indecies[array.farrays[inpt_cnt].offset] * array.farrays[inpt_cnt].width + width];

                }
            }

        }

        boost::shared_ptr<TestbedGL::Geometry>geometry(new Geometry());
        for(unsigned int inpt_cnt = 0; inpt_cnt < array.farrays.size(); inpt_cnt++)
        {
            FloatArray float_array(array.farrays[inpt_cnt].width,
                                   cindecies.size()*(array.farrays[inpt_cnt].width)/array.farrays[inpt_cnt].width, array.farrays[inpt_cnt].width,
                                   array.farrays[inpt_cnt].output_array,array.farrays[inpt_cnt].semantic);

            if (array.farrays[inpt_cnt].semantic == "POSITION")
                geometry->link.set_vertex_array(float_array);
            else if (array.farrays[inpt_cnt].semantic == "NORMAL")
                geometry->link.set_normal_array(float_array);
            else if (array.farrays[inpt_cnt].semantic == "TEXCOORD")
                geometry->link.add_tex_coord_array(float_array);

        }
        std::string id = (*git)->attributes["id"];
        geometry->name = id;
        geometries[id] = geometry;

        FaceArray faces(indecies.size(),vector_to_uarray(indecies));
        geometry->faces = faces;
        geometry->initVBO();
    }

    std::vector<boost::shared_ptr<Entry> > cnodes = ps.r().g("COLLADA")[0]->g("library_visual_scenes")[0]->g("visual_scene")[0]->
            g("node");


    nodes[filename] = boost::shared_ptr<SceneNode>(new SceneNode());

    for(std::vector<boost::shared_ptr<Entry> >::iterator nit = cnodes.begin(); nit != cnodes.end(); nit++)
    {
        boost::shared_ptr<SceneNode> node(new SceneNode);
        node->name = (*nit)->attributes["id"];
        for(std::vector<boost::shared_ptr<Entry> >::iterator vit = (*nit)->children.begin(); vit != (*nit)->children.end(); vit++)
        {
            if( (*vit)->type == "translate")
            {
                std::vector<float>values;
                string_to_float_array((*vit)->data,values);
                node->base_matrix *= node->base_matrix.translation(Vector3(values[0],values[1],values[2]));
            }
            else if( (*vit)->type == "rotate")
            {
                std::vector<float>values;
                string_to_float_array((*vit)->data,values);
                node->base_matrix *= node->base_matrix.rotation(values[3]/180.0f*3.14159f,Vector3(values[0],values[1],values[2]));
            }
            else if( (*vit)->type == "scale")
            {
                std::vector<float>values;
                string_to_float_array((*vit)->data,values);
                node->base_matrix *= node->base_matrix.scale(Vector3(values[0],values[1],values[2]));
            }
            else if( (*vit)->type == "instance_geometry")
            {
                nodes[filename]->children.push_back(node);
                node->geometry_ids.
                push_back(geometries[std::string((*vit)->attributes["url"]).erase(0,1)]);
            }


        }
    }

   std::vector<boost::shared_ptr<Entry> > effects = ps.r().g("COLLADA")[0]->g("library_effects")[0]->g("effect");

   for(std::vector<boost::shared_ptr<Entry> >::iterator mit = effects.begin(); mit!=effects.end();mit++)
   {
       //get profile common
       Entry profile_common = *((*mit)->g("profile_COMMON")[0]);
       for(std::vector<boost::shared_ptr<Entry> >::iterator tech = profile_common.children.begin(); tech != profile_common.children.end(); tech++ )
       {
          if((*tech)->type == "technique")
          {
            if((*tech)->attributes["sid"]=="phong")
            {
            Entry phong = *((*tech)->g("phong")[0]);
            for(std::vector<boost::shared_ptr<Entry> >::iterator values = phong.children.begin();
                values != phong.children.end();values++)
                {
                    std::cout << (*values)->type << std::endl;
                }
            }
            else
            {
                std::cout << (*tech)->attributes["sid"] << " profile not supported" << std::endl;
            }
          }
       }
   }

    //Load effects





    //if file is loaded we don't need to reload. For this we need to implement Reload function.
//	if(nodes.count(filename))
//		return;
//
//	//In here we retrieve node elements from recently parsed file and put it onto dictionary
//    std::vector<boost::shared_ptr<Collada::DictionaryEntry> >entries;
//    ps.dict.GetElementsByType("node",entries);
//
//	nodes[filename] = boost::shared_ptr<SceneNode>(new SceneNode());
//    for (std::vector<boost::shared_ptr<Collada::DictionaryEntry> >::iterator it = entries.begin(); it != entries.end(); it++)
//    {
//
//        boost::shared_ptr<Collada::Node> node = boost::static_pointer_cast<Collada::Node>(*it);
//        if ((boost::static_pointer_cast<Collada::Node>(*it))->instance_geometry.size())
//        {
//
//            boost::shared_ptr<Collada::Geometry> geo = boost::static_pointer_cast<Collada::Geometry>(ps.dict.GetEntry(boost::static_pointer_cast<Collada::Node>(*it)->instance_geometry));
//            boost::shared_ptr<Geometry> geom = boost::shared_ptr<Geometry>(new Geometry());
//            geom->name =geo->id;
//            //Reading nodes and transformations
//            nodes[node->id] = boost::shared_ptr<SceneNode>(new SceneNode());
//            nodes[filename]->children.push_back(nodes[node->id]);
//            nodes[node->id]->base_matrix = Matrix4::identity();
//            nodes[node->id]->name = node->id;
//            for (std::vector<Collada::Transform>::iterator tr_it = node->transforms.begin(); tr_it != node->transforms.end(); tr_it++)
//            {
//                if (tr_it->name == "translate")
//                {
//                    nodes[node->id]->base_matrix *= nodes[node->id]->base_matrix.translation(Vector3(tr_it->values[0],tr_it->values[1],tr_it->values[2]));
//                }
//                else if (tr_it->name == "scale")
//                {
//                    nodes[node->id]->base_matrix *= nodes[node->id]->base_matrix.scale(Vector3(tr_it->values[0],tr_it->values[1],tr_it->values[2]));
//                }
//                else if (tr_it->name == "rotate")
//                {
//                    nodes[node->id]->base_matrix *= nodes[node->id]->base_matrix.rotation(tr_it->values[3]/180.0f*3.14159f,Vector3(tr_it->values[0],tr_it->values[1],tr_it->values[2]));
//                }
//            }
//
//            //reading geometry
//            for (std::vector<std::string>::iterator tri_it = geo->triangles.begin(); tri_it != geo->triangles.end(); tri_it++)
//            {
//                boost::shared_ptr<Collada::Triangles> tris = boost::static_pointer_cast<Collada::Triangles>(ps.dict.GetEntry(*tri_it));
//                FaceArray faces(tris->indecies->array.size(),vector_to_uarray(tris->indecies->array));
//                geom->faces = faces;
//                for (std::vector<std::string>::iterator src_it = tris->inputs.begin(); src_it != tris->inputs.end(); src_it++)
//                {
//                    std::string name = *src_it;
//                    if (geo->vertices.count(name))
//                    {
//                        for (std::vector<std::string>::iterator v_it = geo->vertices[name].begin(); v_it != geo->vertices[name].end(); v_it++)
//                        {
//                            boost::shared_ptr<Collada::Source>src = boost::static_pointer_cast<Collada::Source>(ps.dict.GetEntry(*v_it));
//                            FloatArray pos(src->stride,
//                                           src->array->array.size()/src->stride,src->stride,
//                                           vector_to_array(src->array->array),
//                                           src->semantic);
//
//                            if (src->semantic.compare("POSITION")==0)
//                                geom->link.set_vertex_array(pos);
//                            else if (src->semantic.compare("NORMAL")==0)
//                                geom->link.set_normal_array(pos);
//                            else if (src->semantic.compare("TEXCOORD")==0)
//                                geom->link.add_tex_coord_array(pos);
//                        }
//                    }
//                    else
//                    {
//                        boost::shared_ptr<Collada::Source>src = boost::static_pointer_cast<Collada::Source>(ps.dict.GetEntry(name));
//                        FloatArray pos(src->stride,src->array->array.size()/src->stride ,src->stride,
//                                       vector_to_array(src->array->array),src->semantic);
//                        if (src->semantic.compare("POSITION")==0)
//                            geom->link.set_vertex_array(pos);
//                        else if (src->semantic.compare("NORMAL")==0)
//                            geom->link.set_normal_array(pos);
//                        else if (src->semantic.compare("TEXCOORD")==0)
//                            geom->link.add_tex_coord_array(pos);
//                    }
//                }
//                geom->initVBO();
//                geometries[geo->id] = geom;
//                nodes[node->id]->geometry_ids.push_back(geometries[geo->id]);
//            }
//        }
//    }
}




}
